#include "stdafx.h"
#include "Camera.h"
#include "debug/Debug.h"
#include "platform/Device.h"
#include "scene/Stage.h"
#include "component/Material.h"
#include "config/EngineCon.h"
#include "scene/Display.h"
#include "render/Render.h"

using namespace unigame;

Camera::Camera(const char* name):
GameObject(name),
m_isProjectDirty(true),
m_far(1000),
m_near(0.3f),
m_fov(60),
m_size(100),
m_isOrthographic(false),
m_mask(1),
m_display(0),
m_renderPool(100)
{
	m_cameraToWorld(2, 2) = -1;
	m_worldToCamera(2, 2) = -1;
	m_type = E_CAMERA_GOT;
}

Camera::~Camera()
{
}

void Camera::update()
{
	GameObject::update();
	if(m_display && m_display->isAspectChange())
	{
		aspect(m_display->getAspect());
	}
	if(m_isProjectDirty = true)
	{
		recalculateProject();
		m_isProjectDirty = false;
	}
	if(getFlag(E_TRANSFORM_CHANGE_GOF))
	{
		recalculateView();
	}
}

void Camera::display(int32u disID)
{
	display(Stage::instance()->getDisplay(disID));
}

void Camera::display(Display* dis)
{
	m_display = dis;
	if(m_display)
	{
		aspect(m_display->getAspect());
	}
}

void Camera::cullObjects()
{
	std::vector<GameObject*> list = Stage::instance()->getObjList();
	m_opaqueList = 0;
	m_transparentList = 0;
	m_renderPool.clear();
	for(int i = 0, size = list.size(); i < size; ++i)
	{
		GameObject* go = list[i];
		while(go)
		{
			if(go->isEnable() && go->isVisible() && (go->layer() & m_mask) > 0)
			{
				if(go->mesh())
				{
					int32u order = 0;
					if(go->material())
					{
						order = go->material()->renderOrder();
					}
					if(order <= OPAQUE_RENDER_ORDER)
					{
						this->addToOpaque(go, order);
					}
					else
					{
						this->addToTransparent(go, order);
					}
				}
				go = go->next();
			}
			else
			{
				go = go->next(true);
			}
		}
	}
}

void Camera::addToOpaque(GameObject* obj, int32u order)
{
	RenderObj* ro = &m_renderPool.addItem()->m_value;
	ro->m_obj = obj;
	ro->m_next = 0;
	if(!m_opaqueList)
	{
		m_opaqueList = ro;
	}
	else
	{
		RenderObj* temp = m_opaqueList;
		RenderObj* lastTemp = 0;
		int32u tempOrd;
		Texture* tex = obj->material()? obj->material()->texture(): 0;
		Texture* tempTex;
		while(temp)
		{
			if(temp->m_obj->material())
			{
				tempOrd = temp->m_obj->material()->renderOrder();
				tempTex = temp->m_obj->material()->texture();
			}
			else
			{
				tempOrd = 0;
				tempTex = 0;
			}
			if(tempOrd >= order && tempTex >= tex)
			{
				if(lastTemp)
				{
					lastTemp->m_next = ro;
				}
				else
				{
					m_opaqueList = ro;
				}
				ro->m_next = temp;
				return;
			}
			lastTemp = temp;
			temp = temp->m_next;
		}
		lastTemp->m_next = ro;
	}
}

void Camera::addToTransparent(GameObject* obj, int32u order)
{
	RenderObj* ro = &m_renderPool.addItem()->m_value;
	if(!m_transparentList)
	{
		m_transparentList = ro;
	}
	else
	{
		RenderObj* temp = m_transparentList;
		RenderObj* lastTemp = 0;
		int32u tempOrd;
		Texture* tex = obj->material()? obj->material()->texture(): 0;
		Texture* tempTex;
		Vector3f pos;
		Vector3f tempPos;
		obj->transform().getLocaleToWorld().getPosition(pos);
		ro->m_z.z = m_worldToCamera[M_INDEX(2, 0)] * pos.x + m_worldToCamera[M_INDEX(2, 1)] * pos.y + m_worldToCamera[M_INDEX(2, 2)] * pos.z;
		while(temp)
		{
			if(temp->m_z.minusInfinity == MINUS_INFINITY)
			{
				temp->m_obj->transform().getLocaleToWorld().getPosition(tempPos);
				temp->m_z.z = m_worldToCamera[M_INDEX(2, 0)] * tempPos.x + m_worldToCamera[M_INDEX(2, 1)] * tempPos.y + m_worldToCamera[M_INDEX(2, 2)] * tempPos.z;
			}
			if(temp->m_obj->material())
			{
				tempOrd = temp->m_obj->material()->renderOrder();
				tempTex = temp->m_obj->material()->texture();
			}
			else
			{
				tempOrd = 0;
				tempTex = 0;
			}
			if(tempOrd >= order && temp->m_z.z >= ro->m_z.z && tempTex >= tex)
			{
				if(lastTemp)
				{
					lastTemp->m_next = ro;
				}
				else
				{
					m_transparentList = ro;
				}
				ro->m_next = temp;
				return;
			}
			lastTemp = temp;
			temp = temp->m_next;
		}
		lastTemp->m_next = ro;
	}
}

void Camera::render()
{
	Render::instance()->setProjectMatrix(m_projectMatrix);
	Render::instance()->setViewMatrix(m_worldToCamera);
	Render::instance()->test();
	renderOpaque();
	renderTransparent();
}

void Camera::renderOpaque()
{
	Render::instance()->enableDepth(true);
}

void Camera::renderTransparent()
{
	Render::instance()->enableDepth(false);
}

void Camera::recalculateProject()
{
	if(m_isOrthographic)
	{
		float h = 1 / m_size;
		float w = h * m_aspect;
		m_projectMatrix(0, 0) = w;
		m_projectMatrix(1, 1) = h;
		m_projectMatrix(2, 2) = 2 / (m_near - m_far);
		m_projectMatrix(2, 3) = (m_near + m_far) / (m_near - m_far);
		m_projectMatrix(3, 3) = 1;
	}
	else
	{
		float h = 1 / tan(MULT_TO_RAD * m_fov / 2);
		float w = h * m_aspect;
		float z = m_far / (m_far - m_near);
		m_projectMatrix(0, 0) = w;
		m_projectMatrix(1, 1) = h;
		m_projectMatrix(3, 2) = -1;
		m_projectMatrix(2, 2) = (m_far + m_near) / (m_near - m_far);
		m_projectMatrix(2, 3) = 2 * m_far * m_near / (m_near - m_far);
		m_projectMatrix(3, 3) = 0;
	}
}

void Camera::recalculateView()
{
	memcpy(&m_cameraToWorld, &(m_transform.getLocaleToWorld()), sizeof(Matrixf));
	Vector3f scale;
	m_cameraToWorld.removeScale(scale);
	m_cameraToWorld(0, 2) = -m_cameraToWorld(0, 2);
	m_cameraToWorld(1, 2) = -m_cameraToWorld(1, 2);
	m_cameraToWorld(2, 2) = -m_cameraToWorld(2, 2);
	memcpy(&m_worldToCamera, &(m_transform.getWorldToLocale()), sizeof(Matrixf));
	m_worldToCamera.removeScaleForInverse(scale);
	m_worldToCamera(2, 0) = -m_worldToCamera(2, 0);
	m_worldToCamera(2, 1) = -m_worldToCamera(2, 1);
	m_worldToCamera(2, 2) = -m_worldToCamera(2, 2);
	m_worldToCamera(2, 3) = -m_worldToCamera(2, 3);
}